import requests
import json
import re
import os
import subprocess
import sys
from tqdm import tqdm  # 进度条库

"""清理非法文件名字符"""
def sanitize_filename(name):
    # 替换全角逗号为半角逗号
    name = name.replace("，", ",")
    # 移除Windows非法字符
    invalid_chars = r'[<>:"/\\|?*\x00-\x1F]'
    return re.sub(invalid_chars, '', name).strip()

"""获取url响应体"""
def getResponse(url):
    headers = {
        'referer': 'https://www.bilibili.com/',
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36'
    }
    
    try:
        response = requests.get(url=url, headers=headers, stream=True)
        response.raise_for_status()
        return response
    except requests.RequestException as e:
        print(f"请求失败: {e}")
        sys.exit(1)

"""解析响应体"""
def parseResponse(url):
    print("正在解析视频信息...")
    response = getResponse(url)
    
    try:
        html_data = re.search(r'<script>window.__playinfo__=(.*?)</script>', response.text).group(1)
        jsonData = json.loads(html_data)
        
        videoTitle = re.search(r'<title data-vue-meta="true">(.*?)</title>', response.text).group(1)
        
        videoInfo = {
            'videoTitle': sanitize_filename(videoTitle),
            'audioUrl': jsonData['data']['dash']['audio'][0]['baseUrl'],
            'videoUrl': jsonData['data']['dash']['video'][0]['baseUrl']
        }
        print("获取Response信息成功！")
        return videoInfo
    except (AttributeError, json.JSONDecodeError, KeyError) as e:
        print(f"解析响应失败: {str(e)}")
        sys.exit(1)

"""保存媒体文件"""
def saveMedia(fileName, url, mediaType,save_dir):
    file_path = os.path.join(save_dir, f"{fileName}.{mediaType}")
    
    
    print(f"正在下载{mediaType}...")
    try:
        response = getResponse(url)
        total_size = int(response.headers.get('content-length', 0))
        
        with open(file_path, 'wb') as f, tqdm(
            desc=file_path,
            total=total_size,
            unit='iB',
            unit_scale=True,
            unit_divisor=1024,
        ) as bar:
            for data in response.iter_content(chunk_size=1024):
                size = f.write(data)
                bar.update(size)
        print(f"保存{mediaType}成功！")
        return file_path
    except Exception as e:
        print(f"保存{mediaType}失败: {str(e)}")
        sys.exit(1)

"""合并音视频"""
def AvMerge(Mp3Name, Mp4Name, savePath):
    ffmpeg_path = r'D:\ffmpeg\ffmpeg\bin\ffmpeg.exe'
    
    if not os.path.exists(ffmpeg_path):
        print("FFmpeg未找到，请检查安装路径")
        sys.exit(1)
        
    command = [
        ffmpeg_path,
        '-i', f'"{Mp4Name}"',
        '-i', f'"{Mp3Name}"',
        '-c:v', 'copy',
        '-c:a', 'aac',
        '-strict', 'experimental',
        f'"{savePath}"',
        '-y'  # 自动覆盖输出文件
    ]
    
    print("正在执行FFmpeg命令:")
    print(" ".join(command))
    
    try:
        result = subprocess.run(
            " ".join(command),
            shell=True,
            check=True,
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE
        )
        print("合并成功！")        
        os.remove(Mp4Name)
        return True
    except subprocess.CalledProcessError as e:
        print(f"FFmpeg错误输出:\n{e.stderr.decode()}")
        print(f"合并失败，返回码: {e.returncode}")
        return False

def main():
    # 处理命令行参数中的URL
    if len(sys.argv) > 1:
        url = sys.argv[1]
    else:
        url = input("请输入B站视频url地址:")
    
    # 解析视频信息
    videoInfo = parseResponse(url)
    fileName = videoInfo['videoTitle']
    

    #创建视频标题的文件
    base_dir = 'D:\\bilibili'
    save_dir = os.path.join(base_dir, fileName)
    os.makedirs(save_dir, exist_ok=True)

    # 下载媒体文件
    mp3_path = saveMedia(fileName, videoInfo['audioUrl'], 'mp3',save_dir)
    mp4_path = saveMedia(fileName, videoInfo['videoUrl'], 'mp4',save_dir)
    
    # 合并媒体文件
  
    savePath = os.path.join(save_dir, f"merge_{fileName}.mp4")
    AvMerge(mp3_path, mp4_path, savePath)

if __name__ == '__main__':
    main()